const { ExternalModule } = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin')

/** 1. 实现目标：
 * - 自动实现extrnals
 * - 自动向产出的html文件插入脚本，
 * 
 * 2. 实现过程
 * - 通过AST语法树检测当前的项目脚本中引入了哪些模块，是不是引入了jquery
 * - 如果发现引入了，就要 自动插入CDN脚本
 * - AsyncSeriesBailHook如果监听到有该事件就直接返回，如果没有的话，会继续下面的执行
 */
class AutoExternalPlugin {
  constructor(options){
    this.options = options;
    this.extrnalModules = Object.keys(this.options);  // 如['jquery','lodash']
    this.importedModules = new Set(); // 存放所有导入的外部依赖模块

  }
  apply(compiler){
    // 每种模块都会有一个对应的模块工厂来创建这个模块，普通模块对应的工作就是普通模块工厂
    compiler.hooks.normalModuleFactory.tap('AutoExternalPlugin',(normalModuleFactory) => {
      // 拿到普通工厂之后，注册一个hooks钩子,每个工厂都有一个解析器
      // webpack4加入来模块类型之后，Parser获取需要指定类型moduleType，一般用javascript/auto
      normalModuleFactory.hooks.parser
      .for('javascript/auto')
       // 每拿到一个模块，都会让编译器去解析，如parser babel  reperima  acorn(webpack用的是acorn库)可以把源代码抽象成语法树
        // 遍历到不同类型的节点，会触发不同的钩子，执行对应的事件函数
      .tap('AutoExternalPlugin', parser => { // 解析器是一个hookMap，对应每个类型的模块，会有对应的hookMap
         parser.hooks.import.tap('AutoExternalPlugin', (state, source) => { // 拦截import导入的插件
          console.log(source,'source---')
           if(this.extrnalModules.includes(source)){
              this.importedModules.add(source)
           }
         })

         parser.hooks.call.for('require').tap('AutoExternalPlugin', (expression) => {
          //  拦截require导入
           let value = expression.arguments[0].value
          if(this.extrnalModules.includes(value)){
            this.importedModules.add(value)
          }

         })

      })

      // 改造创建模块的过程
      normalModuleFactory.hooks.factorize.tapAsync('AutoExternalPlugin', (resolveData, callback) => {
        let request = resolveData.request;
        if(this.extrnalModules.includes(request)){
          // 如果这个模块是外部模块，进行拦截
          let expose = this.options[request].expose
          // 创建一个外部模块，并且返回，如jquery = window.JQuery
          // webpack 中的extrnal依赖是通过ExternalModulePlugin实现的，
          // ExternalModulePlugin会通过tap, NormalModuleFactory在在每次创建Module是否是ExternalModule
          callback(null, new ExternalModule(expose, 'window', request))

        }else{
          callback() // 如果正常模块，会直接调用callback向后执行
        }

      })
    })
    
    compiler.hooks.compilation.tap('AutoExternalPlugin', (compilation) => {
      HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tapAsync('AutoExternalPlugin', (htmlData, callback) => {
        let {assetTags} = htmlData
        // 找到实际引入来哪些模块，如jquery, lodash
        let importedExternalModules = Object.keys(this.options).filter(item => this.importedModules.has(item))
        console.log(this.options, this.importedModules)
        importedExternalModules.forEach(key => { // ['jquery']
          assetTags.scripts.unshift({
            tagName: 'script',
            voidTag: false,
            url: this.options[key].url,
            attributes: {
              src: this.options[key].url,
              defer: false
            }
          })
        })
        console.log(assetTags,'assetTags===',this.options)
        callback(null, htmlData)
      })

    })

  }

}
module.exports = AutoExternalPlugin